package kuzmenkov.applet;

import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Checkbox;
import java.awt.CheckboxGroup;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Image;
import java.awt.Label;
import java.awt.MenuItem;
import java.awt.Panel;
import java.awt.PopupMenu;
import java.awt.ScrollPane;
import java.awt.event.MouseAdapter;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import com.jhlabs.awt.ClockLayout;

/**
 * The applet that shows images storing no the server in the XML files
 * 
 * @author Michael Kuzmenkov
 * 
 */
public class ViewerApplet extends Applet {

	/**
	 * 
	 */
	private static final long serialVersionUID = -1784993989467180348L;

	private Logger log = Logger.getLogger(this.getClass().getPackage()
			.getName());

	/**
	 * Delimits paths to the screen files in the server response
	 */
	public static final String DELIMITER = ";";

	public static final String VIEW_MENU = "View";
	public static final String MOVE_FORWARD_MENU_ITEM = "Forward";
	public static final String MOVE_BACK_MENU_ITEM = "Back";
	public static final String MOVE_AT_THE_BEGINNING_MENU_ITEM = "Beginning";
	public static final String MOVE_AT_THE_END_MENU_ITEM = "End";
	public static final String REMOVE_MENU_ITEM = "Remove";

	public static final String ICON_VIEW_BUTTON = "Icon";

	public static final String CIRCLE_VIEW_BUTTON = "Circle";

	private static final String SERVLET_URL_PATTERN = "ViewerServlet";

	/**
	 * The font name for generating a screen image
	 */
	private String fontNameInScreen = "Courier";
	/**
	 * The font size for generating a screen image
	 */
	private int fontSizeInScreen = 12;
	/**
	 * The screen component width on the applet
	 */
	private int screenCompWidth = 150;
	/**
	 * The screen component height on the applet
	 */
	private int screenCompHeight = 150;

	/**
	 * A current image component on the applet
	 */
	private TitledImageComp currImageComp;

	/**
	 * The dialog that shows a scalled image
	 */
	private ImageDlg imageDlg;

	private PopupMenu popup;

	private FlowLayout flowLayout = new FlowLayout(FlowLayout.LEFT);
	private ClockLayout clockLayout = new ClockLayout();

	/**
	 * The size of the view panel that shows the screen components
	 * when it is managed by a FlowLayout
	 */
	private Dimension panelSizeForFlowLayout;
	/**
	 * The size of the view panel that shows the screen components
	 * when it is managed by a ClockLayout
	 */
	private Dimension panelSizeForClockLayout;

	/**
	 * The panel that contains image components
	 */
	private Panel viewPanel;

	public ImageDlg getImageDlg() {
		return imageDlg;
	}

	public PopupMenu getPopup() {
		return popup;
	}

	@Override
	public void init() {

		List<Screen> screens = null;
		try {
			screens = initData();
		} catch (Exception e) {
			log.severe("Data of the applet was not initialized: " + e);
			e.printStackTrace();
		}

		createGUI(this, screens);

	}

	private URLConnection getServletConnection() throws IOException {
		URL servletURL = new URL(getCodeBase(), SERVLET_URL_PATTERN);
		URLConnection con = servletURL.openConnection();
		con.setUseCaches(false);
		con.setDefaultUseCaches(false);
		con.setDoInput(true);
		return con;
	}

	/**
	 * Loads the list of the screen files on the server
	 * 
	 * @return paths delimiting by ViewerApplet.DELIMITER
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	private String loadListScreensFromServlet() throws IOException,
			ClassNotFoundException {
		URLConnection con = getServletConnection();

		InputStream inStr = con.getInputStream();
		BufferedInputStream bis = new BufferedInputStream(inStr);
		InputStreamReader isr = new InputStreamReader(bis);
		BufferedReader br = new BufferedReader(isr);

		String str = null;
		StringBuffer resp = new StringBuffer();
		while ((str = br.readLine()) != null)
			resp.append(str);
		br.close();

		String list = resp.toString();
		log.fine("list = " + "'" + list + "'");

		return list;
	}

	/**
	 * Initializes data to be showed in the applet
	 * 
	 * @return the list of the generated screens
	 * @throws Exception
	 */
	private List<Screen> initData() throws Exception {

		String list;
		try {
			list = loadListScreensFromServlet();
		} catch (Exception e) {
			log
					.severe("The list of the screens hasn't been loaded from the server: "
							+ e.getMessage());
			e.printStackTrace();
			throw e;
		}

		List<URL> urls = parseResponse(getCodeBase(), list);
		log.fine("Formed URLs: " + urls.size());

		List<Screen> screens = loadScreens(urls);
		log.fine("Loaded screens: " + screens.size());

		initScreenImages(screens);

		return screens;
	}

	/**
	 * Initializes the images for each the screens
	 * 
	 * @param screens
	 *            the screens
	 */
	private void initScreenImages(List<Screen> screens) {
		assert (screens != null);
		for (Screen screen : screens) {
			Image image = screen.createImage(getFontNameInScreen(),
					getFontSizeInScreen());
			assert (image != null);
			screen.setImage(image);
		}
	}

	private List<Screen> loadScreens(List<URL> urls) {
		assert (urls != null);
		if (urls.isEmpty())
			throw new IllegalStateException("The list of URLs contain no URL");

		List<Screen> screens = new ArrayList<Screen>(urls.size());
		XMLScreenConverter processor = new XMLScreenConverter();

		for (int i = 0; i < urls.size(); i++) {
			URL url = urls.get(i);

			Screen screen = null;
			try {
				Reader reader = openReader(url);
				screen = loadScreen(reader, processor);
				closeReader(reader);

			} catch (Exception e) {
				log.severe("Error occured: " + e.getMessage());
				e.printStackTrace();
			}
			if (screen != null)
				screens.add(screen);
		}

		assert (screens != null);
		return screens;

	}

	private void closeReader(Reader reader) throws Exception {
		try {
			reader.close();
		} catch (IOException e) {
			throw new Exception("One of the streams was not closed. ", e);
		}
	}

	/**
	 * Parses the stream by using the XMLScreenProcessor to create the instance
	 * of Screen. Preconditions: reader != null, processor != null
	 * 
	 * @param reader
	 *            the reader.
	 * @param processor
	 *            the XML screen processor
	 * @return the instance of Screen. Null if an error occurs
	 * @throws Exception
	 */
	private Screen loadScreen(Reader reader, XMLScreenConverter processor)
			throws Exception {
		assert (reader != null);
		assert (processor != null);

		Screen screen = null;
		try {
			screen = processor.load(reader);
		} catch (Exception e) {
			throw new Exception("One of the screens wasn't loaded. ", e);
		}
		return screen;
	}

	/**
	 * Opens the reader of the URL
	 * 
	 * @param url
	 *            the URL
	 * @return the open reader. Null if an error occurs
	 * @throws IOException
	 */
	private Reader openReader(URL url) throws IOException {
		InputStream inputStream = null;
		Reader reader = null;
		try {
			inputStream = url.openStream();
			reader = new BufferedReader(new InputStreamReader(inputStream));
		} catch (IOException e) {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e1) {
					log.severe("While closing the stream '" + url
							+ "' an error occured: " + e1.getMessage());
				}
			}
			throw new IOException("While opening the stream '" + url
					+ "' an error occured. ", e);

		}

		return reader;
	}

	private List<URL> parseResponse(URL baseURL, String response) {
		if (baseURL == null)
			throw new IllegalArgumentException("The param baseURL is null");
		if (response == null || response.isEmpty())
			throw new IllegalArgumentException(
					"The param 'response' is null or empty");

		String[] paths = response.split(DELIMITER);
		assert (paths.length > 0);

		List<URL> urls = new ArrayList<URL>(paths.length);
		for (int i = 0; i < paths.length; i++) {
			String path = paths[i];
			URL url = null;
			try {
				url = new URL(baseURL, path);
			} catch (MalformedURLException e) {
				log.severe("The " + i + " url (baseURL=" + baseURL + ", path="
						+ path + ") cannot be formed: " + e.getMessage());
				e.printStackTrace();
			}
			if (url != null)
				urls.add(url);
		}

		assert (urls != null);
		return urls;
	}

	/**
	 * Creates GUI of the applet
	 * 
	 * @param screens
	 *            the list of the screens
	 */
	private void createGUI(Container parent, List<Screen> screens) {
		BorderLayout rootLayout = new BorderLayout();
		parent.setLayout(rootLayout);

		Panel tuningPanel = new Panel(new FlowLayout(FlowLayout.LEFT));
		viewPanel = new Panel();
		imageDlg = new ImageDlg(null);

		ScrollPane viewScrollPane = new ScrollPane(
				ScrollPane.SCROLLBARS_AS_NEEDED);

		viewScrollPane.add(viewPanel);

		if (screens == null || screens.isEmpty()) {
			viewPanel.add(new Label("No screens was found"));
		} else {
			fillTuningPanel(tuningPanel);
			fillViewPanel(viewPanel, screens);
		}

		add(tuningPanel, BorderLayout.NORTH);
		add(viewScrollPane, BorderLayout.CENTER);

		
		panelSizeForFlowLayout = calcSizeForFlowLayout(screens.size(),
				getScreenCompWidth(), getScreenCompHeight(), getWidth()
						- viewScrollPane.getVScrollbarWidth(), flowLayout);
		panelSizeForClockLayout = calcSizeForClockLayout(screens.size(),
				getScreenCompWidth(), getScreenCompHeight());
		viewPanel.setPreferredSize(panelSizeForFlowLayout);

		MouseAdapter imageDlgMouseListener = new ImageDlgMouseListener();
		imageDlg.addMouseListener(imageDlgMouseListener);
		imageDlg.addMouseMotionListener(imageDlgMouseListener);
	}

	private Dimension calcSizeForClockLayout(int compsQuant, int compWidth,
			int compHeight) {
		
		int diag = (int)Math.sqrt(compWidth*compWidth + compHeight*compHeight);
		
		int len = diag*compsQuant;
		
		int r = (int)((double)len / (2*Math.PI));
		
		int height = 2*r + compHeight;
		int width =  2*r + compWidth;
		Dimension d = new Dimension(width,height);
		return d;
	}

	private Dimension calcSizeForFlowLayout(int compsQuant, int compWidth,
			int compHeight, int panelWidth, FlowLayout layout) {

		int hGap = layout.getHgap();
		int vGap = layout.getVgap();

		log.fine("panelWidth=" + panelWidth + ", appletWidth=" + getWidth());

		int row = (panelWidth - hGap) / (compWidth + hGap);
		log.fine("row=" + row);

		int real_column = (int) ((float) compsQuant / row + 0.999f);
		log.fine("real_column=" + real_column);

		int width = row * (compWidth + hGap) + hGap;
		int height = real_column * (compHeight + vGap) + vGap;
		log.fine("width=" + width + ", height=" + height);

		Dimension d = new Dimension(width, height);
		return d;
	}

	private void fillTuningPanel(Container tuningPanel) {
		assert (tuningPanel != null);
		Label label = new Label("Choose a view: ");

		CheckboxGroup cbg = new CheckboxGroup();
		Checkbox iconViewBox = new Checkbox(ICON_VIEW_BUTTON, cbg, true);
		Checkbox circleViewBox = new Checkbox(CIRCLE_VIEW_BUTTON, cbg, false);

		ChoosingLayoutActionListener l = new ChoosingLayoutActionListener(this);
		iconViewBox.addItemListener(l);
		circleViewBox.addItemListener(l);

		tuningPanel.add(label);
		tuningPanel.add(iconViewBox);
		tuningPanel.add(circleViewBox);
	}

	private void fillViewPanel(Container viewPanel, List<Screen> screens) {
		assert (screens != null);
		assert (viewPanel != null);
		viewPanel.setLayout(flowLayout);

		popup = new PopupMenu("View");

		MenuItem mi1 = new MenuItem(MOVE_FORWARD_MENU_ITEM);
		MenuItem mi2 = new MenuItem(MOVE_BACK_MENU_ITEM);
		MenuItem mi3 = new MenuItem(MOVE_AT_THE_BEGINNING_MENU_ITEM);
		MenuItem mi4 = new MenuItem(MOVE_AT_THE_END_MENU_ITEM);
		MenuItem mi5 = new MenuItem(REMOVE_MENU_ITEM);

		ViewActionListener viewAL = new ViewActionListener(this);
		mi1.addActionListener(viewAL);
		mi2.addActionListener(viewAL);
		mi3.addActionListener(viewAL);
		mi4.addActionListener(viewAL);
		mi5.addActionListener(viewAL);

		popup.add(mi1);
		popup.add(mi2);
		popup.add(mi3);
		popup.add(mi4);
		popup.addSeparator();
		popup.add(mi5);
		viewPanel.add(popup);

		ImageMouseListener imageMouseListener = new ImageMouseListener(this);

		for (Screen screen : screens) {
			String title = screen.getName();
			assert (title != null);
			Image image = screen.getImage();
			assert (image != null);
			TitledImageComp screenComp = new TitledImageComp(image, title);

			screenComp.setPreferredSize(new Dimension(getScreenCompWidth(),
					getScreenCompHeight()));
			screenComp.addMouseListener(imageMouseListener);

			viewPanel.add(screenComp);
		}

	}

	public void setFontNameInScreen(String fontNameInScreen) {
		this.fontNameInScreen = fontNameInScreen;
	}

	public String getFontNameInScreen() {
		return fontNameInScreen;
	}

	public void setFontSizeInScreen(int fontSizeInScreen) {
		this.fontSizeInScreen = fontSizeInScreen;
	}

	public int getFontSizeInScreen() {
		return fontSizeInScreen;
	}

	public void setCurrImageComp(TitledImageComp currImageComp) {
		this.currImageComp = currImageComp;
	}

	public TitledImageComp getCurrImageComp() {
		return currImageComp;
	}

	public void applyIconView() {
		viewPanel.setLayout(flowLayout);
		viewPanel.setPreferredSize(panelSizeForFlowLayout);
		viewPanel.getParent().doLayout();
		viewPanel.doLayout();

	}

	public void applyClockLayout() {
		viewPanel.setLayout(clockLayout);
		viewPanel.setPreferredSize(panelSizeForClockLayout);
		viewPanel.getParent().doLayout();
		viewPanel.doLayout();
	}

	public void setScreenCompHeight(int screenCompHeight) {
		this.screenCompHeight = screenCompHeight;
	}

	public int getScreenCompHeight() {
		return screenCompHeight;
	}

	public void setScreenCompWidth(int screenCompWidth) {
		this.screenCompWidth = screenCompWidth;
	}

	public int getScreenCompWidth() {
		return screenCompWidth;
	}

	/**
	 * Updates the size of the view panel
	 */
	public void updatePanelSizes() {
		int compsCount = viewPanel.getComponentCount();
		panelSizeForFlowLayout = calcSizeForFlowLayout(compsCount, getScreenCompWidth(), 
				getScreenCompHeight(), panelSizeForFlowLayout.width, flowLayout);
		panelSizeForClockLayout = calcSizeForClockLayout(compsCount, getScreenCompWidth(), getScreenCompHeight());
		
		if (viewPanel.getLayout().equals(flowLayout)){
			viewPanel.setPreferredSize(panelSizeForFlowLayout);
		} else if (viewPanel.getLayout().equals(clockLayout)){
			viewPanel.setPreferredSize(panelSizeForClockLayout);
		} else 
			assert(false);
		viewPanel.getParent().doLayout();
	}

}
